#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>

#include "CaffProc/insn.h"
#include "CaffProc/processor.h"
#include "uarch/sim.h"
#include "sim_trace.h"

using namespace manifold::kernel;

extern Simulator Sim;

DEQUE<Insn *> *trace_queue;
int trace_queue_size;

/** Command line arguments.  */
void usage (char *argv0)
{
    fprintf (stderr, "usage: %s [-c <test case #>] [-t <trace bursts>]\n", argv0);
}

/** Debug.  */
void dump_trace_queue ()
{
    DEQUE<Insn *>::iterator it;

    for (int i = 0; i < trace_queue_size; i++)
    {
        if (trace_queue[i].empty ())
            continue;

        fprintf (stderr, "trace_queue[%d] - size: %d\n", i, (int)trace_queue[i].size ());
        for (it = trace_queue[i].begin (); it != trace_queue[i].end (); it++)
        {
            (*it)->dump ();
        }
        fprintf (stderr, "\n");
    }
}

int sim_main(int trace_bursts)
{
    fprintf(stderr," Inside sim_trace\n " );
    char c;
    int test_case;
    bool test_case_p;
    int selector;
    addresses* curr_block;
    addresses* blocks[NUM_REGIONS];
//    extern char *optarg;


/** Added Manifold stuffs! */
    //Clock globalClock(ZESTO_FREQ);
//    Manifold::Init(argc, argv);

    Sim.Manifold_instantiate();
 
    for (int node = 0; node < Sim.settings.num_nodes; node++)
    {
        Sim.Nd[node]->mod_cid[PR_M] = Component::Create <processor, ModuleID> (0, (ModuleID){node, PR_M});
        Sim.Nd[node]->mod[PR_M] = (Module *) Component::GetComponent (Sim.Nd[node]->mod_cid[PR_M]);
        Clock::Register<processor> ((processor*)Sim.Nd[node]->mod[PR_M], &processor::tick, &processor::tock);
        //Sim.Nd[node]->mod[PR_M] = new processor ((ModuleID){node, PR_M});
    }

    for (int node = 0; node < Sim.settings.num_nodes; node++)
    {
        Sim.Nd[node]->manifold_connect();
    }

/** End manifold add. */

    trace_queue_size = Sim.settings.num_nodes * Sim.settings.threads_per_processor;
    trace_queue = new DEQUE<Insn *>[trace_queue_size];

    for (int i = 0; i < NUM_REGIONS; i++)
    {
        switch (i % 3)
        {
        case 0: blocks[i] = new array_addresses;    break;
        case 1: blocks[i] = new sequence_addresses; break;
        case 2: blocks[i] = new random_addresses;   break;
        }
    }

    // Parse command line.  
    trace_bursts = 0;
    test_case = 0;
    test_case_p = false;

    /** Use default TRACE_BURSTS if none specified.  */
    if (trace_bursts == 0)
        trace_bursts = TRACE_BURSTS;

    fprintf (stderr, "Sim Trace - Generating trace: %d\n", trace_bursts);

    //test_case_p = true;
    //test_case = 9;
    if (!test_case_p)
    {
        /** Pseudo-random test cases.  */
        for (int i = 0; i < trace_bursts; i++)
        {
            curr_block = blocks[random () % NUM_REGIONS];
            selector = random () % 100;

            selector =
                (selector < 10) ? 0 :
                (selector < 50) ? 1 :
                (selector < 80) ? 2 :
                (selector < 90) ? 3 : 4;

            switch(selector) {
                case 0:
                    curr_block->node_write (random () % trace_queue_size);
                    break;
                case 1:
                    curr_block->node_read (random () % trace_queue_size);
                    break;
                case 2:
                    curr_block->prod_consumer (random () % trace_queue_size, 
                                               random () % trace_queue_size);
                    break;
                case 3:
                    curr_block->rw_chain (random () % trace_queue_size);
                    break;
                case 4:
                    curr_block->multinode_read (random () % trace_queue_size);
                    break;
            }

            /** Gives a % chance of a random jump in the sequence.  */
            if((random () % 100) <= PROB_OF_RAND_ADVANCE)
                curr_block->rand_sequence();
            else
                curr_block->advance_sequence();
        }
    }
    else
    {
        /** Specialized test cases.  */
        fprintf (stderr, "Testing case: %d\n", test_case);
        curr_block = blocks[random () % NUM_REGIONS];

        switch (test_case) {
            case 0:
                curr_block->node_read (random () % trace_queue_size); 
                break;
            case 1:
                curr_block->node_write (random () % trace_queue_size);
                break;
            case 2:
                curr_block->prod_consumer (random () % trace_queue_size,
                                           random () % trace_queue_size); 
                break;
            case 3:
                curr_block->rw_chain (random () % trace_queue_size);
                break;
            case 4:
                curr_block->multinode_read (random () % trace_queue_size); 
                break;
            case 5:
                curr_block->prod_consumer (trace_queue_size-1, trace_queue_size-1); 
                break;
            case 6:
                curr_block->node_read (random () % trace_queue_size); 
                curr_block->node_write (random () % trace_queue_size); 
                break;
            case 7:
                int node;

                node = random () % trace_queue_size;
                curr_block->node_read (node);
                curr_block->node_write (node);

                node = random () % trace_queue_size;
                curr_block->node_read(node);
                curr_block->node_write(node);
                break;
            case 8:
                curr_block->node_read (random () % trace_queue_size); 
                curr_block->node_read (random () % trace_queue_size); 
                break;
            case 9:
                trace_queue[63].push_front (new Insn (OpMemLd, 0xBEEF0000));
        }
    }

    Sim.emul.init_trace ();

    Sim.run ();
//    Sim.flush ();
    Sim.fini ();

    /** Cleanup.  */
    for (int i = 0; i < trace_queue_size; i++)
    {
        /** Should all be empty.  */
        while (!trace_queue[i].empty ())
        {
            Insn *insn;

            insn = trace_queue[i].front ();
            trace_queue[i].pop_front ();
            delete insn;
        }
    }

    delete [] trace_queue;

    for (int i = 0; i < NUM_REGIONS; i++)
        delete blocks[i];
}

void addresses::advance_sequence ()
{
    index = (index + 1) % PER_REGION;
}

void addresses::rand_sequence ()
{
    index = random () % PER_REGION;
}

void addresses::node_read (int core_id)
{
    trace_queue[core_id].push_front (new Insn (OpMemLd, values[index]));
}

void addresses::node_write (int core_id)
{
    trace_queue[core_id].push_front (new Insn (OpMemSt, values[index]));
}

void addresses::prod_consumer (int core_id, int num_sharers) 
{
    node_write(core_id);
    multinode_read (num_sharers);
}

void addresses::rw_chain(int num_nodes)
{
    int node;

    node = random () % trace_queue_size;

    node_read (node);
    node_write (node);

    for (int i = 0; i < num_nodes; i++)
    {
        node = random () % trace_queue_size;
        node_read(node);
        node_write(node);
    }
}

void addresses::multinode_read(int num_sharers)
{
    int node;
    bool procs_used[trace_queue_size];

    assert (trace_queue_size > 1);
    assert (num_sharers < trace_queue_size);

    for (int i = 0; i < trace_queue_size; i++)
        procs_used[i] = false;

    for (int i = 0; i < num_sharers; i++)
    {
        do {
            node = random () % trace_queue_size;
        } while (procs_used[node] == true);

        procs_used[node] = true;
        node_read (node);
    } 
}

addresses::addresses ()
{
    index = 0;
}

addresses::~addresses ()
{
}

array_addresses::array_addresses ()
{
    paddr_t start_addr;

    start_addr = random () & ~0xf;

    for (int i = 0; i < PER_REGION; i++)
        values[i] = start_addr + (i * Sim.settings.cache_line_size);
}

array_addresses::~array_addresses ()
{
}

sequence_addresses::sequence_addresses()
{
    paddr_t start_addr;

    start_addr = random () & ~0xf;

    for (int i = 0; i < PER_REGION; i++)
    {
        values[i] = start_addr;
        start_addr += (Sim.settings.cache_line_size * (random () & 0x0000003f));
    }
}

sequence_addresses::~sequence_addresses ()
{
}

random_addresses::random_addresses ()
{
    for (int i = 0; i < PER_REGION; i++)
        values[i] = random () & ~0xf;
}

random_addresses::~random_addresses ()
{
}


